为了账号安全,请及时绑定邮箱和手机立即绑定

zk-02-ZAB协议

标签:
Java

1、ZAB 协议简述

在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性。

ZAB 协议 即 ZooKeeper Atomic Broadcast (ZooKeeper原子消息广播协议)。
ZooKeeper 使用一个 单一的主进程 来接收并处理客户端的所有事务请求,并采用 ZAB 的原子广播协议,将服务器数据的状态变更以 事务 Proposal (提议) 的形式广播到所有的副本进程上去。考虑到在分布式环境中,顺序执行的一些状态变更其前后会存在一定的依赖关系,有些状态变更必须依赖于比它早生成的那些状态变更,例如变更C 需要依赖变更A 和变更B 。这样的依赖关系也对 ZAB 协议提出了一个要求:ZAB 协议必须能够保证一个全局的变更序列被顺序应用,也就是说,ZAB 协议需要保证如果一个状态变更已经被处理了,那么所有其依赖的状态变更都应该已经被提前处理掉了。

ZAB 协议的 核心 是定义了对于那些会改变 ZooKeeper 服务器数据状态的事务请求的处理方式,即:

所有事务请求必须由一个全局唯一的服务器来协调处理,这 样 的 服 务 器 被 称 为 Leader 服 务 器 ,而 余 下 的 其 他 服 务 器 则 成 为 Follower 服务器。 Leader 服务器负责将一个客户端事务请求转换成一个 事务 Proposal (提议) ,并 将 该 Proposal 分发给集群中所有的 Follower 服务器。之 后 Leader 服 务 器 需 要 等 待 所 有 Follower 服务器 的 反 馈 ,一旦超过半 数 的 Follower 服务器进行了正确的反馈后,那 么 Leader 就 会 再 次 向 所 有 的 Follower 服 务 器 分 发 Commit 消 息 ,要 求 其 将 前 一 个 Proposal 进行提交。

2、协议介绍

ZAB 协议包括两种基本的模式,分别是 崩溃恢复消息广播

当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。
其中,所谓的 状态同步 是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致。当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入 消息广播模式 了。

当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入 数据恢复模式 :找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。

注意:

ZooKeeper 设计成只允许唯一的一个 Leader 服务器来进行事务请求的处理。Leader 服务器在接收到客户端的事务请求后,会生成对应的 事务 Proposal (提议) 并发起一轮广播协议;而如果集群中的其他机器接收到客户端的事务请求,那么这些非 Leader 服务器会首先将这个事务请求 转发给Leader 服务器。

Leader 服务器出现崩溃退出或机器重启,亦或是集群中已经不存在过半的服务器与该 Leader 服务器保持正常通信时,那么在重新开始新一轮的原子广播事务操作之前,所有进程首先会使用 崩溃恢复协议 来使彼此达到一个一致的状态,于是整个 ZAB 流程就会从 消息广播模式 进入到 崩溃恢复模式

一个机器要成为新的 Leader,必须获得过半进程的支持,同时由于每个进程都有可能会崩溃,因此,在 ZAB 协议运行过程中,前后会出现多个 Leader ,并且每个进程也有可能会多次成为 Leader 。进入 崩溃恢复模式 后,只要集群中存在过半的服务器能够彼此进行正常通信,那么就可以产生一个新的Leader并再次进入 消息广播模式
举个例子来说,一个由 3 台机器组成的 ZAB 服务,通常由 1 个 Leader 、2 个 Follower 服务器组成。某一个时刻,假如其中一个 Follower 服务器挂了,整个 ZAB 集群是不会中断服务的,这是因为 Leader 服务器依然能够获得过半机器(包括 Leader 自己)的支持。

2.1、消息广播

在整个消息广播过程中,Leader 服务器会为每个事务请求生成对应的 Proposal 来进行广播,并且在广播 事务Proposal 之前,Leader 服务器会首先为这个 事务Proposal 分配一个 全局单调递增的唯一ID ,我们称之为 事务ID (即 ZXID ) 。由于 ZAB 协议需要保证每一个消息严格的因果关系,因此必须将每一个 事务Proposal 按照其 ZXID 的先后顺序来进行排序与处理。

具体的,在消息广播过程中,Leader 服务器会为每一个 Follower 服务器都各自分配一个单独的 队列 ,然后将需要广播的 事务Proposal 依次放入这些队列中去,并且根据 FIFO 策略进行消息发送。每一个 Follower 服务器在接收到这个 事务Proposal 之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给 Leader 服务器一个 Ack 响应。当 Leader 服务器接收到 超过半数 FollowerAck 响应后,就会广播一个 Commit消息 给所有的 Follower 服务器以通知其进行事务提交,同时 Leader 自身也会完成对事务的提交,而每一个 Follower 服务器在接收到 Commit消息 后,也会完成对事务的提交。

图片描述

2.2、崩溃恢复

一旦 Leader 服务器出现崩溃,或者说由于网络原因导致 Leader 服务器失去了与过半 Follower 的联系,那么就会进入 崩溃恢复模式
在 ZAB 协议中,为了保证程序的正确运行,整个恢复过程结束后需要选举出一个新的 Leader 服务器,因此,ZAB 协议需要一个高效且可靠的 Leader 选举算法。

ZAB 协议规定了如果一个 事务Proposal 在一台机器上被处理成功,那么应该在所有的机器上都被处理成功,哪怕机器出现故障崩溃。接下来我们看看在崩溃恢复过程中,可能会出现的两个数据不一致性的隐患及针对这些情况 ZAB 协议所需要保证的特性

2.2.1、ZAB 协议需要确保那些已经在服务器上提交的事务最终被所有服务器都提交。

假设一个事务在 Leader 服务器上被提交了,并且已经得到过半 Follower 服务器的 Ack 反馈,但是在它将 Commit消息 发送给所有 Follower 机器之后, Leader 服务器挂了,如图所示。
图片描述

图中的消息 C2 就是一个典型的例子:在集群正常运行过程中的某一个时刻, Server1 是 Leader 服务器,其先后广播了消息 P1、P2、C1 、P3 和 C2 , 其中,当 Leader 服务器将消息 C2(C2是事务Proposal2 的 Commit消息) 发出后就立即崩溃退出了。针对这种情况,ZAB 协议就需要确保事务 Proposal2 最终能够在所有的服务器上都被提交成功,否则将出现不一致。

2.2.2、ZAB 协议需要确保丢弃那些只在 Leader 服务器上被提出的事务

相反,如果在崩溃恢复过程中出现一个需要被丢弃的提案,那么在崩溃恢复结束后需要跳过该事务 Proposal,如图所示。
图片描述

假设初始的 Leader 服务器 Server1 在提出了一个事务 Proposal3 之后就崩溃退出了,从而导致集群中的其他服务器都没有收到这个事务 Proposal。于是,当 Server1 恢复过来再次加入到集群中的时候,ZAB 协议需要确保丢弃 Proposal3 这个事务。

结合上面提到的这两个崩溃恢复过程中需要处理的特殊情况,就决定了 ZAB 协议必须设计这样一个 Leader 选举算法

能够确保提交已经被 Leader 提交的 事务Proposal ,同时丢弃已经被跳过的 事务Proposal

针对这个要求,如果让 Leader 选举算法 能够保证新选举出来的 Leader 服务器拥有集群中所有机器 最高编号(即 ZXID 最大)事务Proposal ,那么就可以保证这个新选举出来的 Leader —定具有所有已经提交的提案。更为重要的是,如果让具有最高编号 事务Proposal 的机器来成为 Leader ,就可以省去 Leader 服务器检查 Proposal 的提交和丢弃工作的这一步操作了。

2.4、数据同步

完成 Leader 选举之后,在正式开始工作(即接收客户端的事务请求,然后提出新的提案)之前, Leader 服务器会首先确认事务日志中的所有 Proposal 是否都已经被集群中过半的机器提交了,即是否完成数据同步。下面我们就来看看 ZAB 协议的数据同步过程。

所有正常运行的服务器,要么成为 Leader ,要么成为 Follower 并和 Leader 保持同步。 Leader 服务器需要确保所有的 Follower 服务器能够接收到每一条 事务Proposal , 并且能够正确地将所有已经提交了的 事务Proposal 应用到内存数据库中去。具体的, Leader 服务器会为每一个 Follower 服务器都准备一个队列,并将那些没有被各 Follower 服务器同步的事务以 Proposal消息 的形式逐个发送给 Follower 服务器,并在每一个 Proposal消息 后面紧接着再发送一个 Commit消息 ,以表示该事务已经被提交。等到 Follower 服务器将所有其尚未同步的 事务Proposal 都从 Leader 服务器上同步过来并成功应用到本地数据库中后, Leader 服务器就会将该 Follower 服务器加入到真正的可用 Follower 列表中,并开始之后的其他流程。

上面讲到的是正常情况下的数据同步逻辑,下面来看 ZAB 协议是如何处理那些需要被丢弃的 事务Proposal 的。
在 ZAB 协议的事务编号 ZXID 设计中, ZXID 是一个 64位 的数字,其中 低32位 可以看作是一个简单的单调递增的计数器,针对客户端的每一个事务请求, Leader 服务器在产生一个新的 事务Proposal 的时候,都会对该计数器进行加1操作;而 高32位 则代表了 Leader 周期 epoch 的编号,每当选举产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中 最大事务 Proposal 的ZXID ,并从该 ZXID 中解析出对应的 epoch 值,然后再对其进行加1操作,之后就会以此编号作为新的 epoch ,并将 低32位置0 来开始生成新的 ZXID

ZAB 协议中的这一通过 epoch 编号来区分 Leader 周期变化的策略,能够有效地避免不同的 Leader 服务器错误地使用相同的 ZXID 编号提出不一样的 事务Proposal 的异常情况,这对于识別在 Leader 崩溃恢复前后生成的 Proposal非常有帮助,大大简化和提升了数据恢复流程。

基于这样的策略,当一个包含了上一个 Leader 周期中尚未提交过的 事务Proposal 的服务器启动时,其肯定无法成为 Leader ,原因很简单,因为当前集群中一定包含一个 Quorum 集合,该集合中的机器一定包含了更髙 epoch事务Proposal ,因此这台机器的 事务Proposal 肯定不是最高,也就无法成为 Leader 了。当这台机器加入到集群中,以 Follower 角色连接上 Leader 服务器之后, Leader 服务器会根据自己服务器上最后被提交的 Proposal 来 和 Follower 服务器的 Proposal 进行比对,比对的结果当然是 Leader 会要求 Follower 进行一个 回退操作—— 回退到一个确实已经被集群中过半机器提交的最新的 事务Proposal

参考《从Paxos到Zookeeper分布式一致性原理与实践》;

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
10
获赞与收藏
8

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消